﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Collections;
using CommonLib;
using System.Net;

namespace ServerServices
{

    public class ServerService : MarshalByRefObject, IServerService
    {

        PServer thisServer;
        PServer primaryServer;

        int inElection; // em processo de eleicao do primary=1; else 0
        int idServerRequester; // id do server que inicia o processo de eleicao

        List<PServer> listOfKnownServers = new List<PServer>();

        ArrayList primaryProposals = new ArrayList();
        int proposalFlag;

        IDictionary<string, User> registedPeers = new Dictionary<string,User>();


        //private SerializableDictionary<string, string> registedPeers = new SerializableDictionary<string, string>();


        #region IServerService Members

        /**
         * Este método é usado para registar os clientes remotamente
         **/
        public User peerRegister(string nome, string port)
        {
            User newUser=null;

            if (this.primaryServer == null)
                this.determineThePrimary();

            //se nao for primário faz forward do request para o primario
            if (this.primaryServer.Status == 0)
                forwardRegisterRequest(this.thisServer.Id,nome, port);
            else
            {
                Console.WriteLine("SERVER: registedPeers called...");
                IPAddress clientIP = (IPAddress)CallContext.GetData("ClientIPAddress");
                Console.WriteLine("SERVER: User {0} {1}:{2} registered", nome, clientIP, port);

                newUser = new User();
                newUser.Name = nome;
                newUser.Port = Int32.Parse(port);
                newUser.Ip = clientIP;

                this.registedPeers.Add(nome, newUser);
            }

            return newUser;
        }

        /**
         * Este método é usado para cancelar o registo de um cliente 
         **/
        public void cancelRegister(string user)
        {
            if (this.primaryServer == null)
                this.determineThePrimary();

            if (this.primaryServer.Status == 0)
                forwardCancelRegisterRequest(this.thisServer.Id, user);
            else
            {
               this.registedPeers.Remove(user);
               Console.WriteLine("SERVER: User {0} removed", user);
            }
        }

        /**
         * Este método retorna um dicionario contendo todos os clientes 
         * registados 
         **/
        public IDictionary<string, User> getPeers()
        {
            Console.WriteLine("SERVER: getPeers called...");
            return this.registedPeers;
        }

        /**
         * Este método retorna um objecto user que contém os dados
         * de cliente com nome user
         **/
        public User getUser(string nome){
            if (registedPeers.ContainsKey(nome))
                return registedPeers[nome];
            else
                return null;
        }

        /**
         * Este método retorna uma lista de objectos User com os dados de cliente 
         * registados no sistem
         **/
        public IDictionary<string, User> getUsers(ArrayList users)
        {
            IDictionary<string, User> toSend = new Dictionary<string, User>();
            foreach (string u in users)
            {
                if(registedPeers.ContainsKey(u))
                    toSend.Add(u, registedPeers[u]);
                else
                    return null;
            }

            return toSend;
        }
        
         /**
         *  Este método será usado pelos servidores pri
         **/
        public void replicate(){

            //se o server for primario faz a replicacao dos dados para os servers que ele conhece
            if (this.thisServer.Role == 1)
            {
                foreach (PServer server in listOfKnownServers)
                {

                    IServerService serverServices = (IServerService)Activator.GetObject(typeof(IServerService),
                    "tcp://" + server.Ip + ":" + server.Port + "/ServerService");
                    serverServices.replicateData(new Dictionary<string, User>(this.registedPeers));
                   
                }
            }
        }
        
        /**
         * Este método actualiza a lista de peers registados actualmente neste server 
         **/
        public void replicateData(IDictionary<string, User> listOfRegisteredPeers)
        {
            this.registedPeers = listOfRegisteredPeers;
        }

        /**
         * Este método permite registar os dados do server this 
         **/
        public void setThisServer(PServer server)
        {
           this.thisServer = server;
        }

        /**
         * Este método retorna a referencia ao server this
         **/
        public PServer getThisServer()
        {
           return this.thisServer;

        }

        /**
         * Este método permite adicionar um Server à lista dos que o server this conhece
         **/
        public void addNewServer(PServer server)
        {
            this.listOfKnownServers.Add(server);
        }

        /**
         * Este método retorna a lista de Servers conhecidos pelo server this 
         **/
        public List<PServer>  getListOfKnownServers()
        {
            return this.listOfKnownServers;
        }

        /**
         * Imprimir os servidors conhecidos 
         **/
        public void  printKnowingServers()
        {
            Console.WriteLine("this server id:" + thisServer.Id + " port:" + thisServer.Port + " role is:" + this.thisServer.Role + " Status:" + this.thisServer.Status);
            Console.WriteLine("primary server:"+this.primaryServer.Id);
            foreach (PServer server in this.listOfKnownServers)
            {
                Console.WriteLine(">>>replica server id:"+server.Id+" port:"+server.Port+" role:"+server.Role+" status:"+server.Status);
            }
        }

        /**
         * Este método actualiza a lista de servers conhecidos 
         **/
        public void setKnowingServers(List<PServer> list)
        {
            this.listOfKnownServers = list;
        }

       
        /**
         * Disconectar o server 
         **/
        public void putOffline()
        {
            Console.WriteLine("Server "+this.thisServer.Id+" disconnected");
            this.thisServer.Status = 0; // disconnected

            foreach (PServer server in this.listOfKnownServers)
            {
                //verifica se o server conhecido está online,se estiver coloca em disconnected
                if (!server.Ip.Equals(this.thisServer.Ip) && server.Status != 0)
                {
                    IServerService servicos = (IServerService)Activator.GetObject(typeof(IServerService),
                    "tcp://" + server.Ip + ":" + server.Port + "/ServerService");

                    List<PServer> listaServers = servicos.getListOfKnownServers();

                    List<PServer> list = new List<PServer>();

                    foreach (PServer aServer in listaServers)
                    {
                        //se o processo conhecido for o mesmo que o que informa, actualiza-se o role para primary
                        //na lista de processos conhecidos da replica
                        if (aServer.Id == this.thisServer.Id)
                        {
                            aServer.Role = 0;
                            aServer.Status = 0;
                        }

                        list.Add(aServer);
                    }

                    servicos.setKnowingServers(list);
                    //servicos.printKnowingServers();

                }
            }
        }

        public int isConnected()
        {
            return thisServer.Status;
        }

        /**
         *  Poe o servidor online
         **/
        public void putOnline()
        {

            this.thisServer.Status = 1; // disconnected

            foreach (PServer server in this.listOfKnownServers)
            {
                //verifica se o server conhecido está online
                if (!server.Ip.Equals(this.thisServer.Ip) && server.Status != 0)
                {
                    IServerService servicos = (IServerService)Activator.GetObject(typeof(IServerService),
                    "tcp://" + server.Ip + ":" + server.Port + "/ServerService");

                    List<PServer> listaServers = servicos.getListOfKnownServers();
                    foreach (PServer aServer in listaServers)
                    {
                        // vai buscar o server k tava offline e coloca em online
                        if (aServer.Id == this.thisServer.Id)
                        {
                            aServer.Status = 1;
                        }

                    }

                }

            }

        }

        /**
         *  Coloca o server em modo de eleicao do primary
         **/
        public void  setInElection(int valor)
        {
            this.inElection = valor;
        }

        /**
         * Retorna o valor do modo de eleicao do primary 
         **/
        public  int getInElection()
        {
            return this.inElection;
        }

        /**
       *  Actualiza o valor do id do server que inicia o processod e eleicao
       **/
         public void  setIdServerRequester(int valor)
        {
            this.idServerRequester = valor;
        }

        /**
         * Retorna o valor id do server que inicia o processo de eleicao
         **/
        public int  getIdServerRequester()
        {
            return this.idServerRequester;
        }


        public void addProposals(int idServer)
        {
            this.primaryProposals.Add(idServer);
        }

        public ArrayList getProposals()
        {
            return this.primaryProposals;
        }

        public int findNewPrimary()
        {
            this.primaryProposals.Sort();
            this.primaryProposals.Reverse();

            // escolhe o maior id das propostas
            return (int)this.primaryProposals[0];
        }

        /**
         *  Retorna um server da lista dos conhecidos
         **/
        public PServer getKnownServer(int id)
        {
            foreach(PServer server in this.listOfKnownServers){
                if (server.Id == id)
                    return server;
            }

            return null;
        }

        /**
         * Este método faz o forward do peerRegister para o server primário
         **/
        private void forwardRegisterRequest(int id,string nome, string port)
        {
            PServer primaryServer = getPrimaryServer();
            IServerService servicos = (IServerService)Activator.GetObject(typeof(IServerService),
            "tcp://" + primaryServer.Ip.ToString() + ":" + primaryServer.Port + "/ServerService");
            servicos.peerRegister(nome,port);
            Console.WriteLine("request register user:["+nome+"] forwarded from server:["+id+"] to primary:["+primaryServer.Id+"]");

        }

        /**
         * Este método faz o forward do cancelRegister para o server primário
         **/
        private void forwardCancelRegisterRequest(int id, string nome)
        {
            PServer primaryServer = getPrimaryServer();
            IServerService servicos = (IServerService)Activator.GetObject(typeof(IServerService),
            "tcp://" + primaryServer.Ip.ToString() + ":" + primaryServer.Port + "/ServerService");
            servicos.cancelRegister(nome);
            Console.WriteLine("request cancel user:[" + nome + "] forwarded from server:[" + id + "] to primary:[" + primaryServer.Id + "]");

        }

       

        public void setProposalFlag(int flag)
        {
            this.proposalFlag = flag; // flag 1 - proposal is added by this server, otherwise flag - 0
        }

        public int getProposalFlag()
        {
            return this.proposalFlag;
        }

        /**************************************************************************/

        /**
         *  Este método determina o primary server
         **/
        public void determineThePrimary()
        {
            List<int> listOfIds = new List<int>();
            
            foreach (PServer server in this.listOfKnownServers)
            {   
                if(server.Status==1)
                    listOfIds.Add(server.Id);
            }

            listOfIds.Sort();
            listOfIds.Reverse();
            int index = listOfIds[0];

            foreach (PServer server in this.listOfKnownServers)
            {
                if (server.Id == index)
                {
                    this.primaryServer = server;
                    break;
                }
            }

        }

        /**
        * Retorna o primary server
        **/
        public PServer getPrimaryServer()
        {
            if (primaryServer.Status == 0)
                determineThePrimary();

            return this.primaryServer;
        }

        #endregion
         
    }
}
